package com.battlebardgames.simplejavacache;

import java.lang.reflect.Proxy;

import com.battlebardgames.simplejavacache.cache.ICache;
import com.battlebardgames.simplejavacache.key.ICacheKeyProvider;

/**
 * The cache engine
 * @author Thomas Cashman
 */
public class CacheEngine {
	/**
	 * The cache key header
	 */
	public static final String CACHE_KEY_HEADER = "SJC_";
	private static ICache m_cache;
	private static ICacheKeyProvider m_keyProvider;
	
	/**
	 * Initialises the cache engine
	 * @param cacheType Cache storage implementation
	 * @param cacheKeyProvider Cache key generator
	 */
	public static void initialise(ICache cacheType, 
			ICacheKeyProvider cacheKeyProvider) {
		m_cache = cacheType;
		m_keyProvider = cacheKeyProvider;
	}
	
	/**
	 * Caches an object
	 * If the object implements an interface which uses @MethodBasedCache 
	 * then the proxy class is generated and cached,
	 * otherwise the object itself is cached
	 * 
	 * @param object Object instance
	 * @return The unique cache key
	 */
	public static String cache(Object object) {
		String key = m_keyProvider.getCacheKey(object);
		if(!m_cache.contains(key)) {
			Class<?> [] interfaces = object.getClass().getInterfaces();
			if(interfaces != null && interfaces.length > 0) {
				boolean isMethodCache = false;
				for(int i = 0; i < interfaces.length; i++) {
					if(interfaces[i].getAnnotations() != null 
							&& interfaces[i].getAnnotations().length > 0) {
						for(int j = 0; j < interfaces[i].getAnnotations().length; j++) {
							if(interfaces[i].getAnnotations()[j].annotationType() 
									== MethodResultCache.class) {
								isMethodCache = true;
								break;
							}
						}
						
						if(isMethodCache)
							break;
					}
				}
				
				if(isMethodCache) {
					m_cache.set(key, Proxy.newProxyInstance(object.getClass().getClassLoader(), 
							object.getClass().getInterfaces(), 
							new CacheInvocationHandler(object)));
				} else {
					m_cache.set(key, object);
				}
			} else {
				m_cache.set(key, object);
			}
		}
		return key;
	}
	
	/**
	 * Returns a cached object
	 * @param key Unique cache key
	 * @return Object instance
	 */
	public static Object getObject(String key) {
		return m_cache.get(key);
	}
	
	/**
	 * Invalidates an object with a given key, removing it from the cache
	 * @param key
	 */
	public static void invalidate(String key) {
		m_cache.invalidate(key);
	}
	
	/**
	 * Returns the cache implementation
	 * @return
	 */
	public static ICache getCache() {
		return m_cache;
	}
	
	/**
	 * Returns the cache key provider
	 * @return
	 */
	public static ICacheKeyProvider getCacheKeyProvider() {
		return m_keyProvider;
	}
}
